﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Net;

namespace WC
{
    namespace PilotClient
    {
        static class Protocol
        {
            public enum MessageID
            {
                Ping = 1,
                Pong = 2,
                ChatMessage = 3,

                TestMsg_Req = 10,
                TestMsg_Ans = 11
            };

            public struct Header
            {
                public Header(byte[] buffer, int buffersize)
                {
                    _buffer = null;
                    _buffersize = 0;
                    _id = 0;
                    _payload_length = 0;

                    Load(buffer, buffersize);
                }
                
                public bool Load(byte[] buffer, int buffersize)
                {
                    Clear();

                    _buffer = buffer;
                    _buffersize = buffersize;

                    if (!IsValid())
                    {
                        Clear();
                        return false;
                    }

                    return true;
                }
                private void Clear()
                {
                    _buffer = null;
                    _buffersize = 0;
                    _id = 0;
                    _payload_length = 0;
                }
                public uint MessageID
                {
                    get { return _id; }
                    set { _id = value; }
                }
                public static int Length
                {
                    get
                    {
                        int header_len = (sizeof(byte) + sizeof(byte) + sizeof(UInt32) + sizeof(UInt32));
                        Debug.Assert(10 == header_len);
                        return header_len;
                    }
                }
                public uint PayloadLength
                {
                    get { return _payload_length; }
                    set { _payload_length = value; }
                }
                public bool IsValid()
                {
                    if (null == _buffer) return false;
                    if (Length > _buffersize) return false;
                    if (_buffer.Length < _buffersize) return false;
                    if ((_stx1 != _buffer[0]) || (_stx2 != _buffer[1])) return false;

                    return true;
                }

                public byte[] GetBytes()
                {
                    byte[] buffer = new byte[Header.Length];
                    int offset = 0;

                    buffer[0] = _stx1;
                    buffer[1] = _stx2;
                    offset += 2;

                    byte[] tmp;
                    tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((Int32)_id));
                    Buffer.BlockCopy(tmp, 0, buffer, offset, tmp.Length);
                    offset += tmp.Length;
                    Debug.Assert(tmp.Length == 4);

                    tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((Int32)_payload_length));
                    Buffer.BlockCopy(tmp, 0, buffer, offset, tmp.Length);
                    offset += tmp.Length;
                    Debug.Assert(tmp.Length == 4);

                    Debug.Assert((10 == offset) && (10 == buffer.Length));
                    return buffer;
                }

                private byte[] _buffer;
                private int _buffersize;

                private const byte _stx1 = 0x60;    // '<'
                private const byte _stx2 = 0x64;    // '@'
                private UInt32 _id;
                private UInt32 _payload_length;
            }
        }

        abstract class ICommand
        {
            abstract public UInt32 ID { get; }
            abstract public void Execute(WC.Net.Link sender, byte[] buffer, int buffersize);
        }
        class Cmd_TestMsg_Req : ICommand
        {
            override public UInt32 ID { get { return _id; } }
            override public void Execute(WC.Net.Link sender, byte[] buffer, int buffersize)
            {
                Echo.TestMsg_Req req = Echo.TestMsg_Req.CreateBuilder().MergeFrom(buffer).Build();

                if (req.Noreply)
                    return;

                Echo.TestMsg_Ans.Builder ans = Echo.TestMsg_Ans.CreateBuilder();
                ans.Seq = req.Seq;
                if (req.HasMessage)
                    ans.Message = req.Message;

                byte[] ansBuffer = ans.Build().ToByteArray();
                //sender.Send(ansBuffer, ansBuffer.Length, null);
            }

            private const UInt32 _id = (UInt32)Protocol.MessageID.TestMsg_Ans;
        }

        static class Conditions
        {
            public static string address = System.Configuration.ConfigurationManager.AppSettings["address"];
            public static ushort port = Convert.ToUInt16(System.Configuration.ConfigurationManager.AppSettings["port"]);
            
            public static int min_msg_per_socket = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["min_msg_per_socket"]);
            public static int max_msg_per_socket = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["max_msg_per_socket"]);
            public static int size_of_msg = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["size_of_msg"]);

            public static int num_thread = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["num_thread"]);
            public static int num_socket_per_thread = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["num_socket_per_thread"]);

            public static int connect_sleep_rate = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["connect_sleep_rate"]);
            public static int connect_sleep_min = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["connect_sleep_min"]);
            public static int connect_sleep_max = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["connect_sleep_max"]);

            public static int begin_send_sleep_rate = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["begin_send_sleep_rate"]);
            public static int begin_send_sleep_min = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["begin_send_sleep_min"]);
            public static int begin_send_sleep_max = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["begin_send_sleep_max"]);

            public static int send_sleep_rate = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["send_sleep_rate"]);
            public static int send_sleep_min = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["send_sleep_min"]);
            public static int send_sleep_max = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["send_sleep_max"]);

            public static int end_send_sleep_rate = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["end_send_sleep_rate"]);
            public static int end_send_sleep_min = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["end_send_sleep_min"]);
            public static int end_send_sleep_max = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["end_send_sleep_max"]);

        }
        class ClientTesting
        {
            public static void DoSomething()
            {
                Random rnd = new Random();

                for (int i = 0; i < Conditions.num_thread; ++i)
                {
                    Thread th = new Thread(new ParameterizedThreadStart(EchoClient_Thread_Proc));
                    th.Start();

                    //if (rnd.Next(100) <= Conditions.create_thread_sleep_rate)
                    //    Thread.Sleep(rnd.Next(Conditions.create_thread_sleep_min, Conditions.create_thread_sleep_max));
                }
            }

            public static void EchoClient_Thread_Proc(Object obj)
            {
                Random rnd = new Random();
                int max_msg;

                Debug.Assert(0 < Conditions.num_socket_per_thread);

                EchoClient[] clients = new EchoClient[Conditions.num_socket_per_thread];
                for (int i = 0; i < Conditions.num_socket_per_thread; ++i)
                {
                    clients[i] = new EchoClient();
                    max_msg = Math.Max(rnd.Next(Conditions.max_msg_per_socket), Conditions.min_msg_per_socket);
                    clients[i].Begin(Conditions.address, Conditions.port, max_msg);
                    //if (!clients[i].Begin(Conditions.address, Conditions.port, max_msg))
                    //    return;
                    if (rnd.Next(1, 100) <= Conditions.connect_sleep_rate)
                        Thread.Sleep(rnd.Next(Conditions.connect_sleep_min, Conditions.connect_sleep_max));
                }

                if (rnd.Next(1, 100) <= Conditions.begin_send_sleep_rate)
                    Thread.Sleep(rnd.Next(Conditions.begin_send_sleep_min, Conditions.begin_send_sleep_max));

                int done = 0;
                while (done < clients.Length)
                {
                    done = 0;
                    foreach (EchoClient c in clients)
                    {
                        if (c.WasDone)
                        {
                            ++done;
                        }
                        else
                        {
                            if (!c.Do(rnd.Next(100)))
                            {
                                Thread.Sleep(3*1000);
                                c.End();
                            }
                        }
                    }
                }

                if (rnd.Next(100) <= Conditions.end_send_sleep_rate)
                    Thread.Sleep(rnd.Next(Conditions.end_send_sleep_min, Conditions.end_send_sleep_max));
            }
        }

        class EchoClient
        {
            public EchoClient()
            {
                //log.InfoFormat("EchoClient()");
                //>>Initialize();
            }
            ~EchoClient()
            {
                //log.InfoFormat("~EchoClient()");
            }

            public bool Begin(string address, ushort port, int max_msg)
            {
                if (null != _link)
                    return false;

                _link = new WC.Net.Link();
                _link.OnReceived += OnReceived;
                _link.OnSent += OnSent;
                _link.OnClosed += OnClosed;
                _max_msg = max_msg;

                log.InfoFormat("[{0}] Start({1}, {2}, {3})", _link.InstanceID, address, port, _max_msg);

                try
                {
                    log.Debug("Connect...");
                    if (!_link.Connect(address, port))
                    {
                        log.Error("lnk1.Connect() failed");
                        _link.Close();
                        return false;
                    }
                    log.Debug("lnk.Connect() succeeded");
                }
                catch (System.Net.Sockets.SocketException e)
                {
                    log.ErrorFormat("Exception: {0}", e.Message);
                    _link.Close();
                    return false;
                }

                return true;
            }
            public bool Do(int cnt)
            {
                if (!_link.IsConnected)
                    return false;

                Random rnd = new Random();
                System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
                byte[] sendBuffer = new byte[Conditions.size_of_msg];

                for (int i = 0; i < cnt; ++i)
                {
                    //string msg = "hello~~~~~~~~~~~~" + _sent_msg;
                    //byte[] buffer = encoding.GetBytes(msg);

                    if (!_link.Send(sendBuffer, sendBuffer.Length, this))
                        return false;
                    ++_sent_msg;

                    if (rnd.Next(1, 100) <= Conditions.send_sleep_rate)
                        Thread.Sleep(rnd.Next(Conditions.send_sleep_min, Conditions.send_sleep_max));

                    if (_sent_msg % 500 == 0)
                    {
                        log.InfoFormat("[{0}] {1} bytes send, {2}/{3}", _link.InstanceID, sendBuffer.Length, _sent_msg, _max_msg);
                    }
                    if (_sent_msg >= _max_msg)
                    {
                        log.InfoFormat("[{0}] {1} bytes send, {2}/{3}", _link.InstanceID, sendBuffer.Length, _sent_msg, _max_msg);
                        return false;
                    }
                }

                return true;
            }
            public bool WasDone { get { return ((_sent_msg >= _max_msg) || !_link.IsConnected); } }
            public void End()
            {
                log.InfoFormat("[{0}] End() -> max_msg = {1}", _link.InstanceID, _max_msg);
                _link.Close();
            }

            protected void OnReceived(Object sender, byte[] buffer, int size)
            {
                Debug.Assert(0 < size);
                log.DebugFormat("[{0}] {1} bytes received", _link.InstanceID, size);
            }

            protected void OnSent(Object sender, byte[] buffer, int size, Object state)
            {
                Debug.Assert(0 < size);
                log.DebugFormat("[{0}] {1} bytes sent", _link.InstanceID, size);
            }

            protected void OnClosed(Object sender)
            {
                log.DebugFormat("[{0}] OnClose()", _link.InstanceID);
            }

            protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
            protected WC.Net.Link _link;
            private int _max_msg;
            private int _sent_msg;


            void Initialize()
            {
                ICommand cmd;

                cmd = new Cmd_TestMsg_Req();
                _commands.Add(cmd.ID, cmd);

                {
                    Protocol.Header hdr = new Protocol.Header();
                    hdr.MessageID = 1;
                    hdr.PayloadLength = 2;
                    hdr.GetBytes();
                }
            }
            void Dispatch(byte[] buffer, int buffersize)
            {
            }

            private System.Collections.SortedList _commands = System.Collections.SortedList.Synchronized(new System.Collections.SortedList());
        }
    }
}
